/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.nio;

import java.io.FileDescriptor;
import sun.misc.Unsafe;


/**
 * 一种直接字节缓冲区，其内容是文件的内存映射区域。
 *
 * <p> 映射字节缓冲区是通过FileChannel.map方法创建。
 * 这个类使用特定于内存映射的文件区域的操作扩展ByteBuffer类。
 *
 * <p> 映射的字节缓冲区及其所表示的文件映射一直有效，直到缓冲区本身被垃圾收集为止。
 *
 * <p> 映射字节缓冲区的内容可以在任何时候改变，
 * 例如，如果映射文件的相应区域的内容被这个或其他程序改变。
 * 这些更改是否发生以及何时发生，都与操作系统相关，因此未指定。
 *
 * <a name="inaccess"></a><p> 
 * 映射字节缓冲区的全部或部分可能在任何时候都无法访问，
 * 例如，如果映射文件被清空。
 * 尝试访问映射字节缓冲区的不可访问区域不会改变缓冲区的内容，并且会导致在访问时或稍后的某个时间抛出未指定的异常。
 * 因此，强烈建议采取适当的预防措施，以避免由这个程序，或由当前运行的程序，除了读或写文件的内容，操作映射文件。
 *
 * <p> 映射字节缓冲区的行为与普通的直接字节缓冲区没有区别。</p>
 *
 *
 * @author Mark Reinhold
 * @author JSR-51 Expert Group
 * @since 1.4
 */

public abstract class MappedByteBuffer
    extends ByteBuffer
{


	// 这有点向后:根据权限，MappedByteBuffer应该是DirectByteBuffer的一个子类，但是为了保持规范的清晰和简单，并且为了优化目的，用相反的方法来做更容易。
	// 这可以工作，因为DirectByteBuffer是一个包私有类。

    // 对于映射缓冲区，一个文件描述符，如果有效，可用于映射操作;
	// 如果缓冲区未映射，则为空。
    private final FileDescriptor fd;

    // 这应该只由DirectByteBuffer构造函数调用
    // 2个构造函数，一个fd有，一个fd为null
    MappedByteBuffer(int mark, int pos, int lim, int cap, // package-private
                     FileDescriptor fd)
    {
        super(mark, pos, lim, cap);
        this.fd = fd;
    }

    MappedByteBuffer(int mark, int pos, int lim, int cap) { // package-private
        super(mark, pos, lim, cap);
        this.fd = null;
    }

    // 校验fd是否为null，判断是否已映射
    private void checkMapped() {
        if (fd == null)
            // 只有在luser显式强制转换直接字节缓冲区时才会发生
            throw new UnsupportedOperationException();
    }

    // 返回缓冲区与映射的页面对齐地址之间的距离(以字节为单位)。每次计算避免存储在每个直接缓冲区。
    private long mappingOffset() {
        int ps = Bits.pageSize();
        // offset为地址 mod 页大小
        long offset = address % ps;
        // 如果offset >= 0，返回offset
        // 如果offset < 0，返回ps + offset
        return (offset >= 0) ? offset : (ps + offset);
    }

    /**  返回address - mappingOffset，应该是返回 页的初始位置
     * 
     * 举个例子：address为93，pagesize为10，mappingOffset为3，mappingAddress为90
     * @param mappingOffset
     * @return
     */
    private long mappingAddress(long mappingOffset) {
        return address - mappingOffset;
    }

    /** 返回capacity() + mappingOffset，应该是 缓冲区对应的页的位置
     * 
     * 举个例子：address为93，pagesize为10，mappingOffset为3，mappingAddress为90
     * capacity为15，mappingLength为18，对应的页的最后位置是mappingAddress + mappingLength = 108
     * @param mappingOffset
     * @return
     */
    private long mappingLength(long mappingOffset) {
        return (long)capacity() + mappingOffset;
    }

    /**
     * 告知该缓冲区的内容是否驻留在物理内存中。
     *
     * <p> 返回值为true意味着该缓冲区中的所有数据很可能都驻留在物理内存中，
     * 因此可以在不引起任何虚拟内存页结果或I/O操作的情况下被访问。
     * 返回值为false并不一定意味着缓冲区的内容不在物理内存中。
     *
     * <p> 返回值是一个提示，而不是一个保证，
     * 因为底层操作系统可能在调用此方法返回时已经调出了一些缓冲区的数据。</p>
     *
     * @return  <tt>true</tt> if it is likely that this buffer's content
     *          is resident in physical memory
     */
    public final boolean isLoaded() {
    	// 先校验fd是否为null，否则抛出异常
        checkMapped();
        if ((address == 0) || (capacity() == 0))
            return true;
        long offset = mappingOffset();
        long length = mappingLength(offset);
        // 调用 private native boolean isLoaded0(long address, long length, int pageCount);
        return isLoaded0(mappingAddress(offset), length, Bits.pageCount(length));
    }

    // 未使用，但是存储的潜在目标，详细信息请参阅load()。
    private static byte unused;

    /**
     * 将此缓冲区的内容加载到物理内存中。
     *
     * <p> T此方法尽最大努力确保在返回时，该缓冲区的内容驻留在物理内存中。
     * 调用这个方法可能会导致一些页面错误和I/O操作。</p>
     *
     * @return  This buffer
     */
    public final MappedByteBuffer load() {
        checkMapped();
        if ((address == 0) || (capacity() == 0))
            return this;
        long offset = mappingOffset();
        long length = mappingLength(offset);
        // 调用load0方法
        load0(mappingAddress(offset), length);

        // 从每一页读取一个字节，将其放入内存中。
        // 在我们进行的过程中会计算校验和，以防止编译器将循环视为死代码。
        Unsafe unsafe = Unsafe.getUnsafe();
        int ps = Bits.pageSize();
        int count = Bits.pageCount(length);
        long a = mappingAddress(offset);
        byte x = 0;
        // a为每页的第一个字节，然后x与它的值进行异或，最后unused赋值x
        for (int i=0; i<count; i++) {
            x ^= unsafe.getByte(a);
            a += ps;
        }
        if (unused != 0)
            unused = x;

        return this;
    }

    /**
     * 强制将对该缓冲区内容所做的任何更改写入包含映射文件的存储设备。
     *
     * <p> 如果映射到此缓冲区的文件驻留在本地存储设备上，那么当此方法返回时，
     * 就可以保证自创建缓冲区以来或自最后调用此方法以来对该缓冲区所做的所有更改都已写入该设备。
     *
     * <p> 如果文件不驻留在本地设备上，那么就没有这样的保证。
     *
     * <p> 如果这个缓冲区没有在读/写模式下映射(FileChannel.MapMode.READ_WRITE)，那么调用这个方法没有任何效果。</p>
     *
     * @return  This buffer
     */
    public final MappedByteBuffer force() {
        checkMapped();
        if ((address != 0) && (capacity() != 0)) {
            long offset = mappingOffset();
            force0(fd, mappingAddress(offset), mappingLength(offset));
        }
        return this;
    }

    private native boolean isLoaded0(long address, long length, int pageCount);
    private native void load0(long address, long length);
    private native void force0(FileDescriptor fd, long address, long length);
}
